#include "Triangle.h"
#include "Ray.h"
#include "ContactData.h"
#include "Texture.h"
#include "Debug.h"

bool Triangle::Hit(const Ray& ray, float& tmin, ContactData& data) const
{
    //BARYCENTRIC CO-ORDINATES SPACE TEST

    Vec3 oneToZero = v0 - v1;
    Vec3 twoToZero = v0 - v2;
    Vec3 rDir = ray.m_dir;
    Vec3 originToZero = v0 - ray.m_origin;

    //Could also be done with several 3x3 matrix multiplications
    float m0 = (twoToZero.m_y * rDir.m_z)           - (rDir.m_y * twoToZero.m_z);
    float m1 = (originToZero.m_y * rDir.m_z)        - (rDir.m_y* originToZero.m_z);
    float m2 = (twoToZero.m_y * originToZero.m_z)   - (originToZero.m_y * twoToZero.m_z);

    //used for calculating the denominator
    float denom0 = (rDir.m_y * oneToZero.m_z) - (oneToZero.m_y * rDir.m_z);
    float denom1 = (oneToZero.m_y * twoToZero.m_z) - (twoToZero.m_y * oneToZero.m_z);

    //Usefull to store this here,
    float inverse_denom = 1.0f / ( (oneToZero.m_x * m0) + (twoToZero.m_x * denom0) + (rDir.m_x * denom1));

    //First parameter to the main equation, usefull for early outs
    float p0 = (originToZero.m_x * m0) - (twoToZero.m_x * m1) - (rDir.m_x * m2);
    float BETA = p0 * inverse_denom;

    //First segment of the 2d shape made by this triangle, if less than 0
    //then does not satisfy the equation '0 <= (v0 * p0 + v1 * p1 + v2 * p2) <= 1'
    if(BETA < 0.0f)
        return false;

    float m3 = (oneToZero.m_y * originToZero.m_z) - (originToZero.m_y * oneToZero.m_z);
    float p1 = (oneToZero.m_x * m1) + (originToZero.m_x * denom0) + (rDir.m_x * m3);
    float GAMMA = p1 * inverse_denom;

    //Second segment, same as above, must satisfy the equation
    if(GAMMA < 0.0f)
        return false;

    // If we are here then the point lies within the parallelogram
    // created by the two already checked edges.
    // as we have two, and we know that BETA + GAMMA + PHETA must be less than 1, if 
    // these two are > 1, we know PHETA *can't* satisfy PHETA >= 0
    if(BETA + GAMMA > 1.0f)
        return false;

    float m4 = (oneToZero.m_x * m2) - (twoToZero.m_x * m3) + (originToZero.m_x * denom1);
    float PHETA = m4 * inverse_denom;

    //Hard coded epsilon value...stops jittering
    if(PHETA < 0.001f)
        return false;
    if(m_texture)
        data.SetColor(GetTextureColor(BETA,GAMMA));
    //the point lies within the triangle, along dir multiplied by distance PHETA.
    tmin = PHETA;
    data.m_hitNormal = m_normal;
    if(m_interpolateNormal)
        data.m_hitNormal = InterpolateNormal(BETA,GAMMA);
    data.m_hitPoint = ray.m_origin + (ray.m_dir * tmin);
    ray.m_depth = tmin; 
    data.m_hitTarget = (Renderable*)this;
    return true;
}
void Triangle::Create(const Vec3& p0, const Vec3& p1, const Vec3& p2)
{
    v0 = p0;
    v1 = p1;
    v2 = p2;
    CalculateNormal();
}
void Triangle::CalculateNormal()
{
    m_normal = Vector::CalculateNormalTri(v0,v2,v1);
}

Color Triangle::GetTextureColor( float BETA, float GAMMA ) const
{
    return SampleTexture(InterpolateU(BETA,GAMMA), InterpolateV(BETA,GAMMA));
    //return SampleTexture(BETA,GAMMA);
}

Color Triangle::SampleTexture( float U, float V ) const
{
    return m_texture->SampleTexture(U,1-V);
}

float Triangle::InterpolateU( float BETA, float GAMMA ) const
{
    return( ((1 - BETA - GAMMA) * m_uv0.m_x) 
            + (BETA * m_uv1.m_x) 
            + (GAMMA * m_uv2.m_x) );
}

float Triangle::InterpolateV( float BETA, float GAMMA ) const
{
    return( ((1 - BETA - GAMMA) * m_uv0.m_y) 
             + (BETA * m_uv1.m_y) 
             + (GAMMA * m_uv2.m_y) );
}

Vec3 Triangle::InterpolateNormal(float BETA, float GAMMA ) const
{
    Vec3 normal;
    normal = ( (m_n0 * (1 - BETA - GAMMA)) + (m_n1 * BETA) + (m_n2 * GAMMA));
    normal.normalise();
    return(normal);
}
#ifdef DEBUGGING

bool Triangle::Hit_Debug( const Ray& ray, float& tmin, ContactData& data ) const
{
    //BARYCENTRIC CO-ORDINATES SPACE TEST

    Vec3 oneToZero = v0 - v1;
    Vec3 twoToZero = v0 - v2;
    Vec3 rDir = ray.m_dir;
    Vec3 originToZero = v0 - ray.m_origin;

    //Could also be done with several 3x3 matrix multiplications
    float m0 = (twoToZero.m_y * rDir.m_z)           - (rDir.m_y * twoToZero.m_z);
    float m1 = (originToZero.m_y * rDir.m_z)        - (rDir.m_y* originToZero.m_z);
    float m2 = (twoToZero.m_y * originToZero.m_z)   - (originToZero.m_y * twoToZero.m_z);

    //used for calculating the denominator
    float denom0 = (rDir.m_y * oneToZero.m_z) - (oneToZero.m_y * rDir.m_z);
    float denom1 = (oneToZero.m_y * twoToZero.m_z) - (twoToZero.m_y * oneToZero.m_z);

    //Usefull to store this here,
    float inverse_denom = 1.0f / ( (oneToZero.m_x * m0) + (twoToZero.m_x * denom0) + (rDir.m_x * denom1));

    //First parameter to the main equation, usefull for early outs
    float p0 = (originToZero.m_x * m0) - (twoToZero.m_x * m1) - (rDir.m_x * m2);
    float BETA = p0 * inverse_denom;

    //First segment of the 2d shape made by this triangle, if less than 0
    //then does not satisfy the equation '0 <= (v0 * p0 + v1 * p1 + v2 * p2) <= 1'
    if(BETA < 0.0f)
        return false;

    float m3 = (oneToZero.m_y * originToZero.m_z) - (originToZero.m_y * oneToZero.m_z);
    float p1 = (oneToZero.m_x * m1) + (originToZero.m_x * denom0) + (rDir.m_x * m3);
    float GAMMA = p1 * inverse_denom;

    //Second segment, same as above, must satisfy the equation
    if(GAMMA < 0.0f)
        return false;

    // If we are here then the point lies within the parallelogram
    // created by the two already checked edges.
    // as we have two, and we know that BETA + GAMMA + PHETA must be less than 1, if 
    // these two are > 1, we know PHETA *can't* satisfy PHETA >= 0
    if(BETA + GAMMA > 1.0f)
        return false;

    float m4 = (oneToZero.m_x * m2) - (twoToZero.m_x * m3) + (originToZero.m_x * denom1);
    float PHETA = m4 * inverse_denom;

    //Hard coded epsilon value...stops jittering
    if(PHETA < 0.001f)
        return false;
    if(m_texture && m_textured)
        data.SetColor(GetTextureColor(BETA,GAMMA));
    //the point lies within the triangle, along dir multiplied by distance PHETA.
    tmin = PHETA;
    data.m_hitNormal = m_normal;
    data.m_hitPoint = ray.m_origin + (ray.m_dir * tmin);
    data.m_hitTarget = (Renderable*)this;
    return true;
}
#endif